/*
Copyright 2022.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package controllers

import (
	"context"
	"fmt"
	"time"

	"k8s.io/apimachinery/pkg/runtime"
	ctrl "sigs.k8s.io/controller-runtime"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/log"

	traefikv1 "gitee.com/go-course/go8/projects/k8soperator/api/v1"
)

// ServiceReconciler reconciles a Service object
type ServiceReconciler struct {
	client.Client
	Scheme *runtime.Scheme
}

//+kubebuilder:rbac:groups=traefik.magedu.go8,resources=services,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=traefik.magedu.go8,resources=services/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=traefik.magedu.go8,resources=services/finalizers,verbs=update

// Reconcile is part of the main kubernetes reconciliation loop which aims to
// move the current state of the cluster closer to the desired state.
// TODO(user): Modify the Reconcile function to compare the state specified by
// the Service object against the actual cluster state, and then
// perform operations to make the cluster state reflect the state specified by
// the user.
//
// For more details, check Reconcile and its Result here:
// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.13.1/pkg/reconcile
func (r *ServiceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
	l := log.FromContext(ctx)

	// TODO(user): your logic here
	l.Info("start ...")

	// 1. 获取对象(用户期望的状态)
	obj := &traefikv1.Service{}
	if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
		// 记录下保存信息
		l.Error(err, fmt.Sprintf("get object %s failed", req.NamespacedName))
		return ctrl.Result{}, nil
	}

	// 打印下对象
	l.Info("get an object",
		"name", req.NamespacedName,
		"entrypoint", obj.Spec.EntryPoint,
		"url", obj.Spec.URL,
	)

	// 2. 判断对象的状态
	if obj.Status.IsSync {
		// 已经同步, 无需做状态处理
		l.Info("object has synced", "name", req.NamespacedName)
		return ctrl.Result{}, nil
	}

	// 3. 操作对象 让其变成期望的状态(这里不写操作Traefik的业务路径)
	l.Info("sync to traefik success")

	// 4. 操作成功后，修改对象的状态, 变成期望状态
	obj.Status.IsSync = true
	obj.Status.UpdateTime = time.Now().Unix()

	// 5. 对象状态同步到API Server, 需要Status() 才能修改到资源的状态
	if err := r.Client.Status().Update(ctx, obj); err != nil {
		l.Error(err, "update k8s object error")
	}

	return ctrl.Result{}, nil
}

// SetupWithManager sets up the controller with the Manager.
func (r *ServiceReconciler) SetupWithManager(mgr ctrl.Manager) error {
	// 编写对应的Resoruce Controller 扩展对象
	// For(&appsv1.Deployment{}) 编写Deployment的Controller
	return ctrl.NewControllerManagedBy(mgr).
		For(&traefikv1.Service{}).
		Complete(r)
}
